/************************************************************************
 *				SRLOS Team CopyRight
 *@file		mem_mmu_page.h
 *@author	bloceanc
 *@date		7/17/2010
 *@note		virtual memory page management(page managerment)
 ************************************************************************/

#include "os_config.h"
#include "mem_config.h"
#include "mem_processmmu.h"
#include "mem_mmu_page.h"
#include "mem_physical.h"
#include "task.h"

/**
 * 注意:
 * 	内核分配内存给用户程序时，是以“页”为单位进行分配的。内核只进行页单位的内存管理，而
 * 不做详细的管理，更为细化的管理交给用户使用的运行库实现！ 
 *  kernel will not allocate physical page now, but do it when a page exception occured
 * then, kernel will check the address whether less brk address, if large or equal, faild! 
 * otherwise allocate physical page and map it!
 */
 
extern k_process *g_current_process;


extern void *mem_physical_page_allocate(int page_count, int mode);
extern void mem_physical_page_free(void *phy_page_addr, unsigned int page_count);

extern int mt_mutex_lock(k_mutex *mutex, long long time);
extern void mt_mutex_unlock(k_mutex *mutex);

extern void task_current_thread_lock(void);
extern void task_current_thread_unlock(void);

extern void comm_global_critical_section_enter(void);
extern void comm_global_critical_section_leave(void);

extern int mem_physical_page_get_fork(void *phy_page_addr);
extern void mem_physical_page_sub_fork(void *phy_page_addr);
extern void mem_physical_page_sub_ref(void *phy_page_addr);

extern void system_faital_error(void);

/*
 * Zero virtual memory in page unit
 */
void mem_mmu_clear_zero(unsigned int *vir_addr, int page_count)
{
	unsigned int count = page_count<<12;
	unsigned int rectime = count >> 2;
	unsigned int i;

	if(vir_addr == NULL)
		return;
	for(i = 0; i < rectime; i++)
	{
		vir_addr[i]=0;
	}
}

/*
 * copy one page data to another page,they must in same address space!
 */
void mem_mmu_page_copy(unsigned int *src_vir_addr, unsigned int *dst_vir_addr, int page_count)
{
	unsigned int i;
	unsigned int copy_time = page_count << 10; // (page_count << 12)>>10
	for(i = 0;i < copy_time;i++)
	{
		dst_vir_addr[i] = src_vir_addr[i];
	}
}

/*
 * Allocate OS Space Page(s)
 * we'd better allocate physical page as soon as possible;
 * we must make kernel running faster!
 * NOTE: Page mapping has been done at bootloader about OS kernel.
 */
void *__mmu_os_virtual_allocate(unsigned int page_count)
{
	unsigned int *addr_page = mem_physical_page_allocate(page_count, 1);
	if(addr_page == NULL)
	{
		return NULL;
	}
	
	// change to virtual address
	return (void *)(COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR(addr_page));
}

/*
 *	Allocate User Space
 * 		Infact, we do this in page unit!
 *@param:	[in]	size		size of memory wanted
 *@param:	[out]	realsize	real size of allocated
 *@note:	we don't allocate physical page at this function.
 * 			Just allocate virtual page space (page table(s)),it's will
 * 			allocate physical page when process access these pages:A 
 * 			unvalible page table exception occured!
 * 			allocate a virtual page information structure
 */
void *mmu_usr_virtual_allocate(unsigned int size, unsigned int *realsize)
{
	void *res = NULL;
	
	*realsize = (size + ((1<<12)-1))&~((1<<12)-1);
#if	_KERNEL_DEBUG_
	if((*realsize) & 0xfffff000)
	{
		// there must be some error!
		// we test the reasult
		system_faital_error();
	}
#endif

	// disable current process thread switch
	task_current_thread_lock();
	if((g_current_process->mem.brk_end + (*realsize)) < g_current_process->mem.stack_start)
	{
		res = (void *)g_current_process->mem.brk_end;
		g_current_process->mem.brk_end += (*realsize);
	}
	// enable current process thread switch
	task_current_thread_unlock();
	return res;
}

/*
 * Free System mode memory
 * NOTE: we can't modify any slpt or flpt of system space! we must fix the mapping always^_^
 */
void __mmu_os_vritual_free(unsigned int page_viraddr, unsigned int page_count)
{
	unsigned int i;
	
	// in kernel, allocate in page unit, so must free in page unit!
	if(page_viraddr & MEM_CONFIG_PAGE_SIZE)
	{
		system_faital_error();
	}
	
	// just free physical pages.
	for(i = 0; i < page_count; i++)
	{
		mem_physical_page_free((void *)(COMM_MMU_SYSTEMPAGE_ADDRESS_VIR2PHY(((unsigned int)page_viraddr+(i<<12)))), 1);
	}
}

/*
 * Free Usr memory
 * 	NOTE:	This function just used to free user dymanic memory
 * free from the bottom of process heap!
 */
void mmu_usr_virtual_free(unsigned int size, unsigned int *realsize)
{
	unsigned int i;
	unsigned int physical;
	
	// 4KB unit
	size &= ~(MEM_CONFIG_PAGE_SIZE-1);
	if(size == 0)
	{
		*realsize = 0;
		return;
	}
		
	*realsize = size;
	size = size >> 12;	// get page count to free
	task_current_thread_lock();
	g_current_process->mem.brk_end -= *realsize;
	for(i = 0; i < size; i++)
	{
		// we'd better free physical page first !
		MMU_PAGE_PHYSICALADDRESS(g_current_process->mem.mmu.pt_phy_address,
			((unsigned int)g_current_process->mem.brk_end + (i<<12)),physical)
		mem_physical_page_free((void *)physical,1);
		
		// clear second level page information
		MMU_DESTORY_PAGE_MAPPING_SECOND(g_current_process->mem.mmu.pt_phy_address, 
			((unsigned int)g_current_process->mem.brk_end + (i<<12)))
	}
	task_current_thread_unlock();
}

/*
 * 	allocate main thread stack
 *@param	size	size of stack
 *@param	process	which process wants to allocate
 *@return	top address of allocated stack
 *@NOTE:	This Function just used for allocating Main Thread Stack.
 * 			Can't use this allocate common thread stack, because we can just free physical page, but can't 
 * 			free virtual page(virtual address), this just record stack memory start address!
 */
void *mmu_main_thread_stack_allocate(k_process *process, unsigned int size)
{
	void *address = NULL;
	// reside size 4KB
	size = (size + (MEM_CONFIG_PAGE_SIZE - 1))&~(MEM_CONFIG_PAGE_SIZE-1);
	mt_mutex_lock(&process->thread_mutex, -1);
	if((process->mem.stack_start - size) >= process->mem.brk_end)
	{
		// there no more space
		mt_mutex_unlock(&process->thread_mutex);
		return NULL;
	}
	
	process->mem.stack_start -= size;
	address = (void *)process->mem.stack_start;
	mt_mutex_unlock(&process->thread_mutex);
	return address;
}

/*
 * free flpt memorys
 * PARAM:  phy_flpt  physical address of process flpt.
 * NOTE: We just free user mode memory pages, because system mode pages can't be free.we can't modify PT of system.
 *		but kernel can do it, so we let kernel memory manager it.
 */
void mem_mmu_free_process_memory(unsigned int *phy_flpt)
{
	unsigned int *phy_slpt, *vir_slpt, *vir_flpt, *phy_page;
	unsigned int i,j,start_idx;

	if(phy_flpt == NULL)
		return;

	vir_flpt = (unsigned int *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR(((unsigned int)phy_flpt));

	start_idx = MEM_CONFIG_SYS_SPACE_SIZE >> 20;	// reside in MBs
	for(i = start_idx; i < 4096; i++)
	{
		if(vir_flpt[i]&0x3)
		{
			// we need slpt
			phy_slpt = (unsigned int *)(vir_flpt[i]&0xfffffc00);
			vir_slpt = (unsigned int *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR(((unsigned int)phy_slpt));
			for(j = 0; j < 256; j++)
			{
				if(vir_slpt[j]&0x3)
				{
					// now we can free the physical page...
					phy_page = (unsigned int *)(vir_slpt[j]&0xfffff000);
					mem_physical_page_free(phy_page, 1);
				}
			}
			// now we should free the table SLPT.^_^
			mem_physical_page_free(phy_slpt,1);
		}
	}
}

/*
 *	set page read-only
 *@param flpt
 *@param vir_addr
 */
void mem_mmu_set_readonly(unsigned int *flpt, unsigned int vir_addr)
{
	unsigned int *slpt;
	
	if(flpt == NULL)
		return;
	
	// if this is system address, return.
	if(vir_addr < 0x40000000)
	{
		return;
	}
	flpt = (unsigned int *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR((unsigned int)flpt);
	slpt = (unsigned int *)((flpt[vir_addr>>20])&0xfffffc00);
	if((flpt[vir_addr>>20] & 0x3) == 0)
	{
		return;
	}
	slpt = (unsigned int *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR((unsigned int)slpt);
	if((slpt[(vir_addr<<12)>>24] & 0x3) == 0)
	{
		return;
	}
	
	slpt[(vir_addr<<12)>>24] &= MMU_PAGE_AP_READONLY_MASK;	// more details reading arm menual small page, set ap = 00;
											// R S has been set in bootloader!
}

/*
 * 	ReadOnly Pages Handler (for fork process)
 * 		after fork, parent process will make all the pages being used readonly.
 * when children or parent try to access the page, it will abort.so, we should 
 * create new physical page for every process when abort!
 * 
 * 
 * NOTE: This function have some complex, so, may have do some checks...but,,,er,.,
 * 		I'm so taired to this......
 */
void mem_mmu_page_readonly_handler(k_process *process, unsigned int vir_address)
{
	unsigned int phy_addr = 0;
	unsigned int *page_new = NULL;
	unsigned int *flpt = (unsigned int *)process->mem.mmu.pt_phy_address;
	unsigned int *slpt = NULL;
	unsigned int *slpt_copy = NULL;	/* used for copying slpt to new one, we should copy 4 tables for every physical page */
	unsigned int *vir_flpt = (unsigned int *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR(((unsigned int)flpt));
	slpt = (unsigned int *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR((vir_flpt[vir_address>>20]&0xfffffc00));
	MMU_PAGE_PHYSICALADDRESS(((unsigned int)flpt), vir_address, phy_addr);
	if(phy_addr)
	{
		comm_global_critical_section_enter();	// enter global critical section. many process use same page.
		// first, check whether need to allocate new SLPT.by fork_reference.if make fork, the fork reference will
		// inc.so, if 0, it means that the SLPT is owned by current process, we havn't to allocate new one. if
		// more than 0, it means that the SLPT is shared with other process. we should allocate new one, and sub
		// the fork reference and common reference(THIS IS VERY IMPROTANT)!
		if(mem_physical_page_get_fork((void *)(vir_flpt[vir_address>>20]&0xfffffc00)))
		{
			unsigned int i = 0;
			// we have allocate new one and sub both fork reference and common reference.
			unsigned int *new_phy_slpt = mem_physical_page_allocate(1, 1);	// we need system mode physical page
			if(new_phy_slpt == NULL)
			{
				// ..... we have no more physical pages. so, we have to terminate current process!
				// but there is a problem, what we should do about the fork reference value.
				// we should sub it until 0? or what we should do? if we don't care this, it will waste memory.
				// shit...
				comm_global_critical_section_leave();
				return;
			}
			
			// now, we have to copy table records from readonly one.
			// they are all in system space, so we don't need to allocate system space page, they can copy each other
			// anytime...because thair address is all the same in every process.^_^.just copy^_^
			// copy one physical page, 4 SLPT records! Be care!4 reside!
			slpt_copy = (unsigned int *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR((vir_flpt[(vir_address>>20)&~3]&0xfffffc00));
			mem_mmu_page_copy(slpt_copy, (void *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR(((unsigned int)new_phy_slpt)), 1);
			
			// fill new SLPT in FLPT, one physical page has 4 slpt tables!
			MMU_CREATE_PAGE_MAPPING_FIRST(process->mem.mmu.pt_phy_address, ((unsigned int)new_phy_slpt), (unsigned int)vir_address, MMU_DOMAIN_SYS);
		
			// now, sub both reference
			mem_physical_page_sub_fork((void *)COMM_MMU_SYSTEMPAGE_ADDRESS_VIR2PHY(((unsigned int)slpt)));
			mem_physical_page_sub_ref((void *)COMM_MMU_SYSTEMPAGE_ADDRESS_VIR2PHY(((unsigned int)slpt)));
			
			// must re-get slpt value, we must do modification on new SLPT!because slpt has new virtual address,(it has new 
			// physical address and in OS space, so has new virtual address!must be care!)
			slpt = (unsigned int *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR((vir_flpt[vir_address>>20]&0xfffffc00));
		}
		
		// check whether need to allocate new physical page.
		// check fork reference
		if(mem_physical_page_get_fork((void *)phy_addr))
		{
			// need new physical page
			page_new = mem_physical_page_allocate(1, 0);	// usr mode
			if(page_new == NULL)
			{
				// infact we can schedule thread....also can report error.
			}
			else
			{
				if(slpt[(vir_address<<12)>>24]&0xff0 == 0)	// this means that this page is readonly
				{
					// we have to copy that page data to new page.so, we use kernel space page as third.
					unsigned int *swap = __mmu_os_virtual_allocate(1); // must use system space. we have to swap data in two memory space of different process.
					if(swap == NULL)
					{
						mem_physical_page_free(page_new, 1);

						// here we should terminate the new process.because there is not enough memory!
						return;
					}
					mem_mmu_page_copy((unsigned int *)(vir_address&0xfffff000), swap, 1);
				
					// set new mapping
					MMU_CREATE_PAGE_MAPPING_SECOND((unsigned int)flpt, (unsigned int)page_new, (unsigned int)vir_address, MMU_PAGE_AP_USER, 1, 1);
					// !!!!!!!!!!!!!!!!!!maybe we need Refresh DCache!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!	
					// now we can copy data to new page, allthough they have same virtual address ^_^
					mem_mmu_page_copy(swap, (unsigned int *)(vir_address&0xfffff000), 1);  // Yes!we have copied data from a process to another process!
					// now we can free the swap page..Thanks it!
					__mmu_os_vritual_free((unsigned int)swap, 1);

					// sub the fork references
					mem_physical_page_sub_fork((void *)phy_addr);
					// sub reference
					mem_physical_page_sub_ref((void *)phy_addr);
				}
				else
				{
					// there must be errors!
				}
			}
		}
		else
		{
			// the last one. we don't need allocate new physical page
			// just use the current.modify the current attribute
			slpt[(vir_address<<12)>>24] |= 0x550;
		}
		comm_global_critical_section_leave();
	}
}

/*
 * 	Unpresent page handler
 */
void mem_mmu_page_unpresent_handler(k_process *process, unsigned int vir_addr)
{
	unsigned int *flpt = (unsigned int *)process->mem.mmu.pt_phy_address;
	unsigned int *vir_flpt = (unsigned int *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR(((unsigned int)flpt));
	unsigned int *slpt, *vir_slpt;
	unsigned int *page = NULL;
	
	// check address
	if(vir_addr > process->mem.brk_end && vir_addr < process->mem.stack_start)
	{
		// try to access unmalloced address, this is unallowed.
		return;
	}
	
	// we need to lock thread.
	mt_mutex_lock(&process->thread_mutex, -1);
	// first check whether level one abort
	if((vir_flpt[vir_addr>>20]&0x3) == 0 )
	{
		// we need first create new second level page.
		unsigned int *page_new = mem_physical_page_allocate(1, 1);	// system memory!
		unsigned int *vir_page_new;

		if(page_new == NULL)
		{
			mt_mutex_unlock(&process->thread_mutex);
			return;
		}
		// we must clear the memory. otherwise it will cause unknown errors!
		vir_page_new = (unsigned int *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR((unsigned int)page_new);
		mem_mmu_clear_zero(vir_page_new,1);

		// fill the first level page table with current physical page.
		MMU_CREATE_PAGE_MAPPING_FIRST(((unsigned int)flpt),((unsigned int)page_new),(unsigned int)vir_addr,MMU_DOMAIN_SYS);
	}
	// get slpt
	slpt = (unsigned int *)(vir_flpt[vir_addr>>20]&0xfffffc00);
	vir_slpt = (unsigned int *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR(slpt);
	if(vir_slpt[(vir_addr<<12) >> 24]&0x3)
	{
		mt_mutex_unlock(&process->thread_mutex);
		return;
	}
	// get new page
	page = mem_physical_page_allocate(1,0); // user mode
	if(page == NULL)
	{
		mt_mutex_unlock(&process->thread_mutex);
		return;
	}
	// full with new page to slpt
	MMU_CREATE_PAGE_MAPPING_SECOND((unsigned int)flpt, (unsigned int)page, (unsigned int)vir_addr, MMU_PAGE_AP_USER,1,1);
	mt_mutex_unlock(&process->thread_mutex);
}

